﻿using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Data;
using System.Reflection;
using System.Collections;
using System.Text;
using System.Linq;
using Microsoft.Media.ISO;

namespace Microsoft.Media.ISOProfiler.Converters
{
    public sealed class BoxPropertiesToTextConverter : IValueConverter
    {
#if SILVERLIGHT
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
#else
        public object Convert(object value, Type targetType, object parameter, string language)
#endif
        {
            var box = value as IBox;
            if (box != null)
            {
                var propTree = GetProperties(box);
                var propList = Flatten(propTree);

                var result = new StringBuilder();

                foreach (var item in propList.Select(kvp => new { Key = kvp.Item1, Value = kvp.Item2 }))
                {
                    result.Append(item.Key);
                    result.Append("    ");
                    result.Append(item.Value);
                    result.AppendLine();
                }

                return result.ToString();
            }
            else return null;
        }

#if SILVERLIGHT
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
#else
        public object ConvertBack(object value, Type targetType, object parameter, string language)
#endif
        {
            throw new NotImplementedException();
        }

        static IList<Tuple<string, string>> Flatten(IDictionary<string, object> tree)
        {
            var result = new List<Tuple<string, string>>();
            foreach (var item in tree)
            {
                if (item.Value is string)
                {
                    result.Add(Tuple.Create(item.Key, (string)item.Value));
                }
                else if (item.Value is IDictionary<string, object>)
                {
                    result.Add(Tuple.Create(item.Key, string.Empty));
                    foreach (var child in Flatten((IDictionary<string, object>)item.Value))
                    {
                        result.Add(Tuple.Create(new string(' ', 10) + child.Item1, child.Item2));
                    }
                }
                else throw new NotImplementedException();
            }
            return result;
        }

        static Dictionary<string, object> GetProperties(object objItem)
        {
            var result = new Dictionary<string, object>();
            var t = objItem.GetType();
            var typeInfo = t.GetTypeInfo();
            //var props = t.GetRuntimeProperties();
            var props = typeInfo.DeclaredProperties;

            foreach (PropertyInfo pinfo in props)
            {
                if (pinfo.CanWrite)
                {
                    object value = pinfo.GetValue(objItem);
                    if (IsPrimitive(value))
                    {
                        result.Add(pinfo.Name, value.ToString().Replace("\0", ""));
                    }
                    else if (value is IEnumerable<byte>)
                    {
                        var byteArray = ((IEnumerable<byte>)value).ToArray();
                        //var stringValue = Encoding.UTF8.GetString(byteArray, 0, Math.Max(40, byteArray.Length));
                        //var stringValue = BitConverter.ToString(byteArray).Replace("-", "");

                        const int maxBytes = 1024 * 2;
                        StringBuilder hex = new StringBuilder(Math.Min(maxBytes * 3, byteArray.Length * 3));
                        foreach (byte b in byteArray.Take(maxBytes))
                        {
                            hex.AppendFormat("{0:x2} ", b);
                        }

                        if (maxBytes < byteArray.Length)
                        {
                            hex.AppendLine();
                            hex.Append(string.Format("...Trimmed to 2K of {0}K bytes", byteArray.Length / 1024));
                        }
                        var stringValue = hex.ToString().ToUpper();

                        result.Add(pinfo.Name, stringValue);
                    }
                    else if (value is IEnumerable)
                    {
                        if (value.GetType().GetTypeInfo().GenericTypeArguments.All(type => IsPrimitive(type)))
                        {
                            var list = ((IEnumerable)value).Cast<object>().ToList();
                            result.Add(pinfo.Name + "[" + list.Count + "]", "{ " + string.Join(", ", list.Select(v => v.ToString())) + " }");
                        }
                        else if (value is IList<IList<byte>>)
                        {
                            var children = new Dictionary<string, object>();
                            int i = 0;
                            foreach (var bytes in ((IList<IList<byte>>)value).Select(a => a.ToArray()))
                            {
                                StringBuilder hex = new StringBuilder(bytes.Length * 3);
                                foreach (byte b in bytes)
                                {
                                    hex.AppendFormat("{0:x2} ", b);
                                }

                                children.Add(string.Format("[{0}]", i), hex.ToString().ToUpper());
                                i++;
                            }
                            result.Add(pinfo.Name, children);
                        }
                        else
                        {
                            var children = new Dictionary<string, object>();
                            int i = 0;
                            foreach (var item in (IEnumerable)value)
                            {
                                children.Add(string.Format("[{0}]: {1}", i, item.GetType().Name), GetProperties(item));
                                i++;
                            }
                            result.Add(pinfo.Name, children);
                        }
                    }
                    else if (value != null)
                    {
                        result.Add(pinfo.Name, GetProperties(value));
                    }
                }
            }

            return result;
        }

        private static bool IsPrimitive(object obj)
        {
            return (obj is Boolean ||
                obj is Guid ||
                obj is Decimal ||
                obj is DateTimeOffset ||
                obj is TimeSpan ||
                obj is DateTime ||
                obj is Char ||
                obj is SByte ||
                obj is Byte ||
                obj is Double ||
                obj is Single ||
                obj is UInt16 ||
                obj is UInt32 ||
                obj is UInt64 ||
                obj is Int16 ||
                obj is Int32 ||
                obj is Int64 ||
                obj is Enum ||
                obj is String);
        }

        private static bool IsPrimitive(Type type)
        {
            return (type == typeof(Boolean)) ||
                type == typeof(Guid) ||
                type == typeof(Decimal) ||
                type == typeof(DateTimeOffset) ||
                type == typeof(TimeSpan) ||
                type == typeof(DateTime) ||
                type == typeof(Char) ||
                type == typeof(SByte) ||
                type == typeof(Byte) ||
                type == typeof(Double) ||
                type == typeof(Single) ||
                type == typeof(UInt16) ||
                type == typeof(UInt32) ||
                type == typeof(UInt64) ||
                type == typeof(Int16) ||
                type == typeof(Int32) ||
                type == typeof(Int64) ||
                type == typeof(Enum) ||
                type == typeof(String);
        }
    }
}
